package com.houger.ddd.seckill.template;

import com.alibaba.fastjson2.JSONObject;
import com.houger.UidGenerator;
import com.houger.ddd.common.base.BaseExtDataDto;
import com.houger.ddd.common.utils.BusinessException;
import com.houger.ddd.common.utils.DateUtils;
import com.houger.ddd.common.utils.StatusCode;
import com.houger.ddd.redpacket.facade.dto.UserInfoDto;
import com.houger.ddd.seckill.application.MapperUtils;
import com.houger.ddd.seckill.constant.InstanceStatusEnum;
import com.houger.ddd.seckill.constant.PlatformEnum;
import com.houger.ddd.seckill.constant.TemplateStatusEnum;
import com.houger.ddd.seckill.facade.dto.ActivityInstanceDto;
import com.houger.ddd.seckill.facade.dto.mq.ActivityBasicMqDto;
import com.houger.ddd.seckill.facade.dto.mq.InstanceMqDto;
import com.houger.ddd.seckill.infra.datamanager.ActivityRewardItemDataManager;
import com.houger.ddd.seckill.infra.datamanager.InstanceDataManager;
import com.houger.ddd.seckill.infra.datamanager.SeckillDataManager;
import com.houger.ddd.seckill.repository.mq.RabbitMqComponent;
import com.houger.ddd.seckill.template.context.ActivityInstanceContext;
import com.houger.ddd.seckill.template.service.ActivityInstanceService;
import com.houger.ddd.seckill.template.service.ActivityTemplateService;
import com.houger.ddd.seckill.template.context.ActivityContext;
import com.houger.ddd.seckill.template.holder.BeanHolder;
import com.houger.ddd.seckill.template.annotation.ActivityTemplateConfig;
import com.houger.ddd.seckill.infra.entity.*;
import com.houger.ddd.seckill.infra.datamanager.ActivityContextDataManager;
import com.wf.captcha.SpecCaptcha;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

import static com.houger.ddd.common.utils.StatusCode.DO_NOT_REPEAT;
import static com.houger.ddd.common.utils.StatusCode.NOT_IN_RUN;

/**
 * @author Houger.
 * @date 2024年05月27日 11:16.
 */

@Slf4j
public abstract class ActivityTemplate extends BaseTemplate {

    @Getter
    private Class<? extends BaseExtDataDto> bizExtDataObjClass;
    @Getter
    private Class<? extends BaseExtDataDto> uiExtDataObjClass;
    @Getter
    private Class<? extends BaseExtDataDto> rewardItemExtDataObjClass;
    @Getter
    private Class<? extends BaseExtDataDto> instanceExtDataObjClass;

    private UidGenerator uidGenerator;
    protected ActivityTemplateService activityTemplateService;
    protected ActivityInstanceService activityInstanceService;
    protected ActivityContextDataManager activityContextDataManager;
    protected ActivityRewardItemDataManager activityRewardItemDataManager;
    protected SeckillDataManager seckillDataManager;
    protected InstanceDataManager instanceDataManager;

    protected RabbitMqComponent rabbitMqComponent;

    @Getter
    protected ActivityContext activityContext;
    @Getter
    protected ActivityInstanceContext activityInstanceContext;

    @Override
    public void afterPropertiesSet() {
        this.beanHolder = beanHolder.getBean(BeanHolder.class);
        this.uidGenerator = beanHolder.getBean(UidGenerator.class);
        this.rabbitMqComponent = beanHolder.getBean(RabbitMqComponent.class);
        this.activityTemplateService = beanHolder.getBean(ActivityTemplateService.class);
        this.activityInstanceService = beanHolder.getBean(ActivityInstanceService.class);
        this.activityContextDataManager = beanHolder.getBean(ActivityContextDataManager.class);
        this.activityRewardItemDataManager = beanHolder.getBean(ActivityRewardItemDataManager.class);
        this.seckillDataManager = beanHolder.getBean(SeckillDataManager.class);
        this.instanceDataManager = beanHolder.getBean(InstanceDataManager.class);
        ActivityTemplateConfig config = AnnotationUtils.findAnnotation(this.getClass(), ActivityTemplateConfig.class);
        this.bizExtDataObjClass = config == null ? BaseExtDataDto.class : config.bizExtData();
        this.uiExtDataObjClass = config == null ? BaseExtDataDto.class : config.uiExtData();
        this.rewardItemExtDataObjClass = config == null ? BaseExtDataDto.class : config.rewardItemExtData();
        this.instanceExtDataObjClass = config == null ? BaseExtDataDto.class : config.instanceExtData();
    }


    public void setActivityContext(ActivityContext context) {
        if (context.getBasicDto().getTemplateCode().isBlank()) {
            context.getBasicDto().setTemplateCode(genCode());
        }
        this.activityContext = context;
    }

    public ActivityContext queryActivityContext(Long templateId, String templateCode,Long instanceId) {
        ActivityTemplateBasic templateBasic = null;
        if (templateId != null) {
            templateBasic = activityTemplateService.queryBasicById(templateId);
        }
        if (StringUtils.isNotEmpty(templateCode)) {
            templateBasic = activityTemplateService.queryBasicByCode(templateCode);
        }
        if (!Objects.isNull(instanceId)){
            final ActivityInstance instance = instanceDataManager.queryById(instanceId);
            templateBasic = activityTemplateService.queryBasicById(instance.getTemplateId());
        }
        if (templateBasic == null) {
            throw new BusinessException(StatusCode.ERROR, "ACTIVITY_TEMPLATE_NOT_EXIST");
        }
        ActivityContext context = this.activityContextDataManager.queryContextByTemplateCode(this, templateBasic.getTemplateCode());
        this.activityContext = context;
        return context;
    }

    public ActivityInstanceContext queryActivityInstanceContext(Long instanceId) {
        final ActivityInstanceContext instanceContext = ActivityInstanceContext.builder()
            .instanceDto(MapperUtils.getInstanceDto(this.instanceDataManager.queryById(instanceId),this.instanceExtDataObjClass))
            .build();

        this.activityInstanceContext = instanceContext;
        return instanceContext;
    }

    @Override
    public void destroy() {
        log.info("[{}] destroy!!!", this.getClass().getName());
    }


    //  ------------------ （ C端 ------------------


    public void start(UserInfoDto userInfoDto) {
        preCheck(userInfoDto);
        instance(userInfoDto);
        delayedClose();
    }

    private void delayedClose() {
        final ActivityInstanceDto instanceDto = this.activityInstanceContext.getInstanceDto();
        int sc=DateUtils.getTimeDifference(LocalDateTime.now(),instanceDto.getEndTime()).intValue();
        InstanceMqDto instanceMqDto = InstanceMqDto.builder()
            .instanceId(instanceDto.getId())
            .templateId(instanceDto.getTemplateId())
            .sendTime(DateUtils.getNowFormat())
            .startTime(instanceDto.getStartTime())
            .endTime(instanceDto.getEndTime())
            .fromStatus(InstanceStatusEnum.OPENING.getValue())
            .toStatus(InstanceStatusEnum.CLOSE.getValue())
            .version(instanceDto.getVersion())
            .build();
        this.rabbitMqComponent.sendDelayedMessageForInstance(JSONObject.toJSONString(instanceMqDto),sc);
    }

    protected void close() {
        updateInstanceStatus(InstanceStatusEnum.CLOSE.getValue());
    }

    protected void instance(UserInfoDto userInfoDto) {
        //根据用户、商品生成实例
        ActivityInstance instance = ActivityInstance.builder()
            .templateId(activityContext.getId())
            .templateCode(activityContext.getTemplateCode())
            .instanceCode(genCode())
            .initiatorId(userInfoDto.getUserId())
            .startTime(LocalDateTime.now())
            .endTime(LocalDateTime.now().plusSeconds(activityContext.getBizConfigDto().getInstanceCloseAfter()))
            .instanceStatus(InstanceStatusEnum.OPENING.getValue())
            .platform(PlatformEnum.ALL.getValue())
            .playerMaxLimit(activityContext.getBizConfigDto().getInstancePlayerLimit())
            .playerRewardLimit(activityContext.getBizConfigDto().getInstancePlayerRewardLimit())
            .extData("{}")
            .build();

        this.activityInstanceContext=ActivityInstanceContext.builder()
            .instanceDto(MapperUtils.getInstanceDto(activityInstanceService.save(instance),this.instanceExtDataObjClass))
            .build();
    }

    /**
     * 活动状态是否进行中
     * 活动时间
     * 是否已参与
     *
     * @author Mr.hao
     * @date 2024/5/30 17:50
     */
    protected void preCheck(UserInfoDto userInfoDto) {
        final Integer templateStatus = this.activityContext.getBasicDto().getTemplateStatus();
        if (TemplateStatusEnum.ON_GOING.getValue() != templateStatus) {
            throw new BusinessException(NOT_IN_RUN, null);
        }
        final List<ActivityInstance> activityInstances = this.instanceDataManager.queryInstanceByTemplateIdAndUserId(activityContext.getId(), userInfoDto.getUserId());
        if (!activityInstances.isEmpty()) {
            throw new BusinessException(DO_NOT_REPEAT, null);
        }
    }

    protected void preCheckInstance() {
        final ActivityInstanceDto instanceDto = this.activityInstanceContext.getInstanceDto();
        if (InstanceStatusEnum.ISSUING.getValue() == instanceDto.getInstanceStatus()){
            throw new BusinessException(StatusCode.DO_NOT_REPEAT);
        }
        if (InstanceStatusEnum.TIME_OUT.getValue() == instanceDto.getInstanceStatus()){
            throw new BusinessException(StatusCode.TIME_OUT);
        }
        if (InstanceStatusEnum.OPENING.getValue() != instanceDto.getInstanceStatus()){
            throw new BusinessException(StatusCode.INSTANCE_NOT_IN_RUN);
        }

        if (instanceDto.getStartTime().isAfter(LocalDateTime.now()) || instanceDto.getEndTime().isBefore(LocalDateTime.now())){
            throw new BusinessException(StatusCode.INVALID_PARAM);
        }
    }

    protected abstract void checkRestrict(Long instanceId,Long userId);

    protected void updateInstanceStatus(final int status) {

    }
    //  ------------------ C端 ) ------------------

    //  ------------------ ( 后台 ------------------
    public Long save(String userName) {
        Long templateId;
        String templateCode;
        ActivityTemplateBasic basic = MapperUtils.getBasic(this.activityContext.getBasicDto());
        templateId = this.activityTemplateService.saveTemplateBasic(basic, userName);
        templateCode = basic.getTemplateCode();
        if (this.activityContext.getBizConfigDto() != null) {
            ActivityTemplateBizConfig bizConfig = MapperUtils.getBizConfig(this.activityContext.getBizConfigDto());
            bizConfig.setTemplateId(templateId);
            bizConfig.setTemplateCode(templateCode);
            this.activityTemplateService.saveTemplateBizConfig(bizConfig, userName);
        }
        if (this.activityContext.getUiConfigDto() != null) {
            ActivityTemplateUiConfig uiConfig = MapperUtils.getUiConfig(this.activityContext.getUiConfigDto());
            uiConfig.setTemplateId(templateId);
            uiConfig.setTemplateCode(templateCode);
            this.activityTemplateService.saveTemplateUiConfig(uiConfig, userName);
        }
        if (this.activityContext.getRewardItemDtoList() != null) {
            List<ActivityRewardItem> rewardItemList = MapperUtils.getRewardItemList(this.activityContext.getRewardItemDtoList());
            for (ActivityRewardItem rewardItem : rewardItemList) {
                rewardItem.setTemplateId(templateId);
                rewardItem.setTemplateCode(templateCode);
            }
            this.activityTemplateService.saveTemplateRewardItemList(rewardItemList, userName);
        }
        this.evict();
        this.destroy();
        return templateId;
    }

    private void evict() {
        this.activityContextDataManager.evict(this.activityContext.getTemplateCode());
        this.activityRewardItemDataManager.evict(this.activityContext.getId());
    }


    public int updateStatus(final Long templateId, final int from, final int to) {
        return activityTemplateService.updateStatus(templateId, from, to, null);
    }

    @Transactional(rollbackFor = Exception.class)
    public void enable(final Long id) {
        //1 未启用，2 未开始
        int result = this.updateStatus(id, 1, 2);
        final ActivityContext activityContext = this.queryActivityContext(id, null,null);
        if (result > 0) {
            //发送延迟消息1，活动状态变更：2未开始->3进行中
            final long now = DateUtils.localDateTimeToMillis(LocalDateTime.now());
            long startTime = DateUtils.localDateTimeToMillis(activityContext.getBasicDto().getStartTime());
            final ActivityBasicMqDto startDto = ActivityBasicMqDto.builder()
                .templateId(id)
                .fromStatus(TemplateStatusEnum.READY.getValue())
                .toStatus(TemplateStatusEnum.ON_GOING.getValue())
                .sendTime(DateUtils.getNowFormat())
                .startTime(activityContext.getBasicDto().getStartTime())
                .endTime(activityContext.getBasicDto().getEndTime())
                .version(activityContext.getBasicDto().getVersion())
                .build();
            rabbitMqComponent.sendDelayedMessage(JSONObject.toJSONString(startDto), (int) (startTime - now));

            //发送延迟消息2，活动状态变更：3进行中->4 已结束
            final ActivityBasicMqDto endDto = ActivityBasicMqDto.builder()
                .templateId(id)
                .fromStatus(TemplateStatusEnum.ON_GOING.getValue())
                .toStatus(TemplateStatusEnum.OVER.getValue())
                .sendTime(DateUtils.getNowFormat())
                .startTime(activityContext.getBasicDto().getStartTime())
                .endTime(activityContext.getBasicDto().getEndTime())
                .version(activityContext.getBasicDto().getVersion())
                .build();
            long endTime = DateUtils.localDateTimeToMillis(activityContext.getBasicDto().getEndTime());
            rabbitMqComponent.sendDelayedMessage(JSONObject.toJSONString(endDto), (int) (endTime - now));

            //商品预热 加入缓存中
            activityRewardItemDataManager.queryItemListByTemplateId(id);
        }

//        rabbitMqComponent.sendDelayedMessage("4",10);
//
//        rabbitMqComponent.sendExpirationMessage(rabbitMqComponent.getNow()+"|"+"测试过期消息","2000");
//        rabbitMqComponent.sendOrderMessage(rabbitMqComponent.getNow()+"|"+"死信队列","5000");

    }

    protected String genCode() {
        return String.valueOf(uidGenerator.getUID());
    }




    //  ------------------ 后台 ) ------------------


    public SpecCaptcha captcha(final Long instanceId, final Long userId) {
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
        String verCode = specCaptcha.text().toLowerCase();

        seckillDataManager.evictCaptcha(instanceId,userId);
        // 存入redis并设置过期时间为5分钟
        seckillDataManager.getCaptchaOrSet(instanceId,userId,verCode);
        return specCaptcha;
    }

    public void checkCaptcha(final Long instanceId, final Long userId, final String captcha) {
        // 获取redis中的验证码
        final String originCaptcha = seckillDataManager.getCaptchaOrSet(instanceId, userId, null);
        // 判断验证码
        if (captcha==null || !captcha.trim().toLowerCase().equals(originCaptcha)) {
            throw new BusinessException(StatusCode.VERIFICATION_CODE_IS_INCORRECT);
        }
        seckillDataManager.evictCaptcha(instanceId,userId);
    }


    public void check(final Long instanceId, final Long userId, final Long itemId,final String captcha) {

        //校验验证码
        this.checkCaptcha(instanceId,userId,captcha);
        // 校验活动状态
        this.preCheckInstance();
        //校验活动限制
        this.checkRestrict(instanceId,userId);
    }
}
